package nl.ru.rd.facedetection.nnbfd.neuralnetwork;

/**
 * A backpropagatable neuron layer.
 * 
 * @author Wouter Geraedts (s0814857)
 */
public class BNLayer extends NLayer implements Backpropagatable
{
	private static final long serialVersionUID = -7461113239165512286L;
	private double[] errorDeltas;

	/**
	 * Create a backpropagatable neuron layer.
	 * 
	 * @param previousLayer
	 *            The previous Layer to be bound to.
	 * @param size
	 *            The number of Nodes of this Layer.
	 * @param f
	 *            The activationfunction to be used by the nodes of this Layer.
	 */
	public BNLayer(Layer previousLayer, int size, Activationfunction f)
	{
		super(previousLayer, size, f);

		this.errorDeltas = new double[size];
	}

	public void updateDeltas(double[] targets)
	{
		for(int i = 0; i < this.getSize(); i++)
		{
			double activation = this.getActivation(i);
			double error = targets[i] - activation;
			this.errorDeltas[i] = error * this.getActivationfunction().calculateDerivative(activation);
		}
	}

	public void updateDeltas()
	{
		Backpropagatable layer = (Backpropagatable) this.getNextLayer();

		for(int i = 0; i < this.getSize(); i++)
		{
			double error = layer.calculateErrorSum(this, i);
			this.errorDeltas[i] = error * this.getActivationfunction().calculateDerivative(this.getActivation(i));
		}
	}

	/**
	 * Get the error delta of a Node of this Layer.
	 * 
	 * @param i
	 *            The index of the Node of this Layer.
	 * @return The error delta of the Node of this Layer.
	 */
	protected double getErrorDelta(int i)
	{
		return this.errorDeltas[i];
	}

	public void updateWeights(double learningRate)
	{
		Layer previousLayer = this.getPreviousLayer();

		for(int i = 0; i < this.getSize(); i++)
			for(int j = 0; j < previousLayer.getSize(); j++)
			{
				double change = this.errorDeltas[i] * previousLayer.getActivation(j);
				this.setWeight(i, j, this.getWeight(i, j) + learningRate * change);
			}
	}

	public double calculateErrorSum(Layer previousLayer, int previousLayerNodeId)
	{
		double error = 0;
		for(int j = 0; j < this.getSize(); j++)
			error += this.getErrorDelta(j) * this.getWeight(j, previousLayerNodeId);

		return error;
	}
}
